package com.free.ext.httpclient;

import com.free.exception.SystemException;
import com.free.ext.httpclient.builder.HCB;
import com.free.ext.httpclient.utils.SSLs.SSLProtocolVersion;
import com.free.ext.httpclient.utils.Utils;
import com.free.utils.JsonUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.*;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * 功能概述: 
 * </p>
 * <p>
 * 功能详述: 
 * </p>
 */
public class HttpClientUtil
{
    
    /*========================================================================*
     *                         Public Fields (公共属性)                                                                
     *========================================================================*/
    
    /*========================================================================*
     *                         Private Fields (私有属性)                                                                
     *========================================================================*/
    private static Map<String,HttpClient> cache = new HashMap<>();
    
    private static final Object lock = new Object();
    /*========================================================================*
     *                         Construct Methods (构造方法) 
     *========================================================================*/
    static{
        cache.put("http", HCB.custom().build());
        cache.put("https",HCB.custom().ssl().build());
    }
    
    /*========================================================================*
     *                         Public Methods (公有方法)                                                                   
     *========================================================================*/

    /**
     * 以Get方式，请求资源或服务
     * 
     * @param client                client对象
     * @param url                   资源地址
     * @param headers           请求头信息
     * @param context           http上下文，用于cookie操作
     * @param encoding      编码
     * @return                      返回处理结果
     * @throws
     */
    public static String get(HttpClient client, String url, Header[] headers, HttpContext context, String encoding) throws SystemException {
        return get(HttpConfig.custom().client(client).url(url).headers(headers).context(context).encoding(encoding));
    }
    /**
     * 以Get方式，请求资源或服务
     * 
     * @param config        请求参数配置
     * @return
     * @throws
     */
    public static String get(HttpConfig config) throws SystemException {
        return send(config.method(HttpMethods.GET));
    }
    
    /**
     * 以Post方式，请求资源或服务
     * 
     * @param client                client对象
     * @param url                   资源地址
     * @param headers           请求头信息
     * @param parasMap      请求参数
     * @param context           http上下文，用于cookie操作
     * @param encoding      编码
     * @return                      返回处理结果
     * @throws
     */
    public static String post(HttpClient client, String url, Header[] headers, Map<String,Object>parasMap, HttpContext context, String encoding) throws SystemException {
        return post(HttpConfig.custom().client(client).url(url).headers(headers).map(parasMap).context(context).encoding(encoding));
    }
    
    /**
     * 以Post方式，请求资源或服务
     * 
     * @param config        请求参数配置
     * @return
     * 
     */
    public static String post(HttpConfig config) throws SystemException {
        return send(config.method(HttpMethods.POST));
    }
    
    /**
     * 以Post方式，请求资源或服务
     * 
     * @param config        请求参数配置
     * @return
     *      返回json格式的MAP
     */
    public static Map<String,Object> postForMap(HttpConfig config) throws SystemException {
        try
        {
            String response = post(config);
            return JsonUtils.readValue(response, Map.class);
        }
        catch(Exception ex)
        {
            throw new SystemException("JSON转化失败", ex);
        }
    }
    
    /**
     * 下载文件
     * 
     * @param client                client对象
     * @param url                   资源地址
     * @param headers           请求头信息
     * @param context           http上下文，用于cookie操作
     * @param out                   输出流
     * @return                      返回处理结果
     * @throws
     */
    public static OutputStream down(HttpClient client, String url, Header[] headers, HttpContext context, OutputStream out) throws SystemException {
        return down(HttpConfig.custom().client(client).url(url).headers(headers).context(context).out(out));
    }
    
    /**
     * 下载文件
     * 
     * @param config        请求参数配置
     * @return                      返回处理结果
     * @throws
     */
    public static OutputStream down(HttpConfig config) throws SystemException {
        return fmt2Stream(execute(config.method(HttpMethods.GET)), config.out());
    }
    
    /**
     * 上传文件
     * 
     * @param client                client对象
     * @param url                   资源地址
     * @param headers           请求头信息
     * @param context           http上下文，用于cookie操作
     * @return                      返回处理结果
     * @throws
     */
    public static String upload(HttpClient client, String url, Header[] headers, HttpContext context) throws SystemException {
        return upload(HttpConfig.custom().client(client).url(url).headers(headers).context(context));
    }
    
    /**
     * 上传文件
     * 
     * @param config        请求参数配置
     * @return              返回处理结果
     * @throws
     */
    public static String upload(HttpConfig config) throws SystemException {
        if(config.method() != HttpMethods.POST ){
            config.method(HttpMethods.POST);
        }
        return send(config);
    }
    
    /**
     * 请求资源或服务
     * 
     * @param config        请求参数配置
     * @return
     * @throws
     */
    public static String send(HttpConfig config) throws SystemException {
        return fmt2String(execute(config), config.outenc());
    }
    
    /**
     * 请求资源或服务
     * 
     * @param config        请求参数配置
     * @return              返回HttpResponse对象
     * @throws
     */
    private static HttpResponse execute(HttpConfig config) throws SystemException {
        create(config);//获取链接
        HttpResponse resp = null;
        try {
            //创建请求对象
            HttpRequestBase request = getRequest(config.url(), config.method());
            
            //设置header信息
            request.setHeaders(config.headers());
            
            //判断是否支持设置entity(仅HttpPost、HttpPut、HttpPatch支持)
            if(HttpEntityEnclosingRequestBase.class.isAssignableFrom(request.getClass())){
                List<NameValuePair> nvps = new ArrayList<NameValuePair>();
                
                //检测url中是否存在参数
                config.url(Utils.checkHasParas(config.url(), nvps, config.inenc()));
                
                //装填参数
                HttpEntity entity = Utils.map2HttpEntity(nvps, config.map(), config.inenc());
                
                //设置参数到请求对象中
                ((HttpEntityEnclosingRequestBase)request).setEntity(entity);
                
                Utils.info("请求地址："+config.url());
                if(nvps.size()>0){
                    Utils.info("请求参数："+nvps.toString());
                }
                if(config.json()!=null){
                    Utils.info("请求参数："+config.json());
                }
            }else{
                int idx = config.url().indexOf("?");
                Utils.info("请求地址："+config.url().substring(0, (idx>0 ? idx : config.url().length())));
                if(idx>0){
                    Utils.info("请求参数："+config.url().substring(idx+1));
                }
            }
            //执行请求操作，并拿到结果（同步阻塞）
            resp = (config.context()==null)?config.client().execute(request) : config.client().execute(request, config.context()) ;
            
            if(config.isReturnRespHeaders()){
                //获取所有response的header信息
                config.headers(resp.getAllHeaders());
            }
            
            //获取结果实体
            return resp;
            
        } catch (IOException e) {
            throw new SystemException("HttpClientUtil.execute 发生异常",e);
        }
    }
    
    /*========================================================================*
     *                         Private Methods (私有方法)                                                                   
     *========================================================================*/
    /**
     * 根据请求方法名，获取request对象
     * 
     * @param url                   资源地址
     * @param method            请求方式
     * @return
     */
    private static HttpRequestBase getRequest(String url, HttpMethods method) {
        HttpRequestBase request = null;
        switch (method.getCode()) {
            case 0:// HttpGet
                request = new HttpGet(url);
                break;
            case 1:// HttpPost
                request = new HttpPost(url);
                break;
            case 2:// HttpHead
                request = new HttpHead(url);
                break;
            case 3:// HttpPut
                request = new HttpPut(url);
                break;
            case 4:// HttpDelete
                request = new HttpDelete(url);
                break;
            case 5:// HttpTrace
                request = new HttpTrace(url);
                break;
            case 6:// HttpPatch
                request = new HttpPatch(url);
                break;
            case 7:// HttpOptions
                request = new HttpOptions(url);
                break;
            default:
                request = new HttpPost(url);
                break;
        }
        return request;
    }
    
    private static void create(HttpConfig config)
    {
        if(config.client() == null)
        {
           creatProc(config,getCacheKey(config));
        }
    }

    private static String getCacheKey(HttpConfig config)
    {
        if(StringUtils.isNotEmpty(config.customKey())){
            return config.customKey();
        }
        StringBuffer strBuf = new StringBuffer();
        if(config.url().toLowerCase().startsWith("http://"))
        {
            strBuf.append("http");
        }
        else
        {
            strBuf.append("https");
            if(config.sslProtocol() != null && !config.sslProtocol().equals(SSLProtocolVersion.SSLv3))
            {
                strBuf.append(config.sslProtocol().getName());
            }
        }
        if(config.proxy() != null)
        {
            strBuf.append(":" + config.proxy().getLeft()+":"+config.proxy().getRight().toString());
        }
        return strBuf.toString();
    }
    
    private static void creatProc(HttpConfig config,String key)
    {
        if(!cache.containsKey(key))
        {
            synchronized(lock){
                if(!cache.containsKey(key))
                {
                    HCB hcb = HCB.custom();
                    if(config.proxy() != null)
                    {
                        hcb.proxy(config.proxy().getLeft(), config.proxy().getRight());
                    }
                    if(config.url().toLowerCase().startsWith("http://"))
                    {
                        cache.put(key, hcb.build());
                    }
                    else
                    {
                        if(!config.sslProtocol().equals(SSLProtocolVersion.SSLv3))
                        {
                            hcb.sslpv(config.sslProtocol().getName());
                        }
                        cache.put(key, hcb.ssl().build());
                    }
                }
            }
        }
        config.client((HttpClient)cache.get(key)); 
    }
    
    /**
     * 转化为字符串
     * 
     * @param resp          响应对象
     * @param encoding  编码
     * @return
     * @throws
     */
    private static String fmt2String(HttpResponse resp, String encoding) throws SystemException {
        String body = "";
        try {
            if (resp.getEntity() != null) {
                // 按指定编码转换结果实体为String类型
                body = EntityUtils.toString(resp.getEntity(), encoding);
                Utils.info(body);
            }else{//有可能是head请求
                body =resp.getStatusLine().toString();
            }
            EntityUtils.consume(resp.getEntity());
        } catch (IOException e) {
            throw new SystemException("HttpClientUtil.fmt2String 发生异常",e);
        }finally{           
            close(resp);
        }
        return body;
    }
    
    /**
     * 转化为流
     * 
     * @param resp          响应对象
     * @param out               输出流
     * @return
     * @throws
     */
    private static OutputStream fmt2Stream(HttpResponse resp, OutputStream out) throws SystemException {
        try {
            resp.getEntity().writeTo(out);
            EntityUtils.consume(resp.getEntity());
        } catch (IOException e) {
            throw new SystemException("HttpClientUtil.fmt2Stream 发生异常",e);
        }finally{
            close(resp);
        }
        return out;
    }
    
    /**
     * 尝试关闭response
     * 
     * @param resp              HttpResponse对象
     */
    private static void close(HttpResponse resp) {
        try {
            if(resp == null) return;
            //如果CloseableHttpResponse 是resp的父类，则支持关闭
            if(CloseableHttpResponse.class.isAssignableFrom(resp.getClass())){
                ((CloseableHttpResponse)resp).close();
            }
        } catch (IOException e) {
            Utils.exception(e);
        }
    }
}

